﻿using System;
using System.Collections;
using System.Reflection;
using System.IO;
using System.Collections.Generic;
using System.Text;
using System.Xml;

namespace SPVisualDev.VSAddin
{
    public class Worker : MarshalByRefObject
    {
        private Assembly CurrentDomain_ReflectionOnlyAssemblyResolve(object sender, ResolveEventArgs args)
        {
            return Assembly.ReflectionOnlyLoad(args.Name);
        }


        public string GetTargetAssemblyName(string AssemblyPath)
        {
            if (File.Exists(AssemblyPath))
            {
                AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += new ResolveEventHandler(CurrentDomain_ReflectionOnlyAssemblyResolve);
                Assembly a = Assembly.ReflectionOnlyLoadFrom(AssemblyPath);

                return a.GetName().FullName;
            }
            else
            {
                return string.Empty;
            }
        }

        private bool IsSubClassOf(Type type, Type baseType)
        {
            if (type.BaseType == null)
                return false;

            Type t = type.BaseType;
            while (t != null)
            {
                if (t.FullName == baseType.FullName)
                    return true;

                t = t.BaseType;
            }

            return false;


        }

        public bool HasValidFeatureReceiverBase(string AssemblyPath, string FullTypeName)
        {
            AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += new ResolveEventHandler(CurrentDomain_ReflectionOnlyAssemblyResolve);
            Assembly a;// = Assembly.ReflectionOnlyLoadFrom(AssemblyPath);
            try
            {
                a = Assembly.ReflectionOnlyLoad(AssemblyPath);
            }
            catch (FileNotFoundException)
            {
                return false;
            }

            Type t = a.GetType(FullTypeName);
            if (t != null)
            {
                if (string.Compare(t.BaseType.FullName, "SPDevExtensions.WSSTES.WSSFeatureReceiverBase", true) == 0)
                    return true;
                else
                    return false;

            }
            else
            {
                return false;
            }
        }


        public Dictionary<Guid, string> GetWSSFieldsInAssembly(string AssemblyPath, bool IgnoreNestedTypes)
        {
            AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += new ResolveEventHandler(CurrentDomain_ReflectionOnlyAssemblyResolve);
            Assembly a = Assembly.ReflectionOnlyLoadFrom(AssemblyPath);

            Type[] types = a.GetTypes();
            Dictionary<Guid, string> ret = new Dictionary<Guid, string>();

            for (int i = 0; i < types.Length; i++)
            {
                if (!IgnoreNestedTypes && types[i].IsNested)
                    continue;

                IList<CustomAttributeData> attributes = CustomAttributeData.GetCustomAttributes(types[i]);
                object o = GetNamedAttributeParameter(attributes, "WSSFieldAttributes", "ID");
                if (o != null)
                    ret.Add(new Guid(o.ToString()), types[i].FullName);
            }

            return ret;
        }

        private object GetNamedAttributeParameter(IList<CustomAttributeData> attributes, string AttributeTypeName, string ParameterName)
        {
            object ret = null;
            foreach (CustomAttributeData cad in attributes)
            {
                if (cad.Constructor.DeclaringType.Name == AttributeTypeName)
                {
                    foreach (CustomAttributeNamedArgument cana in cad.NamedArguments)
                    {
                        if (ParameterName == cana.MemberInfo.Name)
                        {
                            ret = cana.TypedValue.Value;
                            break;
                        }
                    }
                }
            }

            return ret;
        }

        public string[] GenerateEventReceiverXML(string FeatureXml, string ManifestXml, string AssemblyPath, string FeatureName, string FeatureId)
        {
            string escaped_feature_name = FeatureName.Replace(".", "_");

            AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += new ResolveEventHandler(CurrentDomain_ReflectionOnlyAssemblyResolve);
            Assembly a = Assembly.ReflectionOnlyLoadFrom(AssemblyPath);

            bool isSigned = false;
            byte[] b = a.GetName().GetPublicKeyToken();
            if (b.Length > 0)
                isSigned = true;

            Type[] types = a.GetTypes();

            XmlDocument xdoc_feature = new XmlDocument();
            //xdoc_feature.PreserveWhitespace = true;
            xdoc_feature.LoadXml(FeatureXml);

            int feature_xml_hash = xdoc_feature.OuterXml.GetHashCode();

            /*
            bool hasFeatureReceiver = false;
            string feature_receiver_assembly = xdoc_feature.DocumentElement.GetAttribute("ReceiverAssembly");
            if (feature_receiver_assembly.ToLower().IndexOf("publickeytoken") != -1)
                hasFeatureReceiver = true;

            if (!hasFeatureReceiver)
            {
                foreach (Type t in types)
                {
                    if (t.BaseType == null)
                        continue;

                    bool f = false;

                    if (IsSubClassOf(t, typeof(Microsoft.SharePoint.SPFeatureReceiver)))
                    {
                        IList<CustomAttributeData> attributes = CustomAttributeData.GetCustomAttributes(t);
                        foreach (CustomAttributeData attribute_data in attributes)
                        {
                            if (attribute_data.ToString().ToLower().IndexOf("categoryattribute(") != -1)
                            {
                                Guid g1 = new Guid(FeatureId);
                                Guid g2 = new Guid(attribute_data.ConstructorArguments[0].Value.ToString());
                                if (g1.Equals(g2))
                                {
                                    if (isSigned)
                                    {
                                        f = true;
                                    }
                                    else
                                    {
                                        return null;
                                    }
                                    break;
                                }
                            }
                        }

                        //if (t.Namespace.EndsWith("." + escaped_feature_name) || f) -- Old implementation. Deprecated
                        if (f)
                        {
                            xdoc_feature.DocumentElement.SetAttribute("ReceiverAssembly", a.FullName);
                            xdoc_feature.DocumentElement.SetAttribute("ReceiverClass", t.FullName);
                        }
                    }
                }
            }
            */

            if (string.IsNullOrEmpty(ManifestXml))
                return new string[] { xdoc_feature.InnerXml, ManifestXml };

            XmlDocument xdoc_manifest = new XmlDocument();
            //xdoc_manifest.PreserveWhitespace = true;
            xdoc_manifest.LoadXml(ManifestXml);

            int elements_xml_hash = xdoc_manifest.OuterXml.GetHashCode();

            XmlNamespaceManager xns_manager = new XmlNamespaceManager(xdoc_manifest.NameTable);
            xns_manager.AddNamespace("ns", Common.C_MS_SHAREPOINT_XMLNS);
            xns_manager.AddNamespace("spe", Common.C_MS_SHAREPOINT_EVENTS_NS);

            ArrayList arr_xml_nodes = new ArrayList();

            foreach (XmlNode xn_element in xdoc_manifest.DocumentElement.ChildNodes)
            {
                if (xn_element.Name == "ListTemplate" || xn_element.Name == "ContentType")
                    arr_xml_nodes.Add(xn_element);
            }


            foreach (XmlNode xn_element in arr_xml_nodes)
            {
                foreach (Type t in types)
                {
                    if (t.BaseType == null)
                        continue;

                    if (!IsSubClassOf(t, typeof(Microsoft.SharePoint.SPItemEventReceiver)) &&
                        !IsSubClassOf(t, typeof(Microsoft.SharePoint.SPListEventReceiver)))
                        continue;

                    bool f = false;

                    //New identification process. Works with reflection attributes.
                    IList<CustomAttributeData> attributes = CustomAttributeData.GetCustomAttributes(t);
                    foreach (CustomAttributeData attribute_data in attributes)
                    {
                        if (attribute_data.ToString().ToLower().IndexOf("categoryattribute(") != -1)
                        {
                            if (xn_element.Name == "ListTemplate")
                            {
                                string s = new Guid(FeatureId).ToString("N").ToUpper() + "_" + Common.GetXmlAttribute(xn_element as XmlElement, "Name").ToUpper();
                                if (attribute_data.ConstructorArguments[0].Value.ToString().ToUpper() == s.ToUpper())
                                {
                                    f = true;
                                    break;
                                }
                            }
                            else if (xn_element.Name == "ContentType")
                            {
                                if (attribute_data.ConstructorArguments.Count > 0)
                                {
                                    if (attribute_data.ConstructorArguments[0].Value.ToString().ToUpper() == Common.GetXmlAttribute(xn_element as XmlElement, "ID").ToUpper())
                                    {
                                        f = true;
                                        break;
                                    }
                                }
                            }
                        }
                    }

                    //Old identification process
                    /*
                     * Deprecated.
                    if (t.Namespace.EndsWith("." + escaped_feature_name))
                    {
                        if (t.Name.StartsWith(xn_element.Attributes["Name"].Value + "_"))
                            f = true;
                    }
                    */

                    //If no matching method found, continue iteration.
                    if (!f)
                        continue;

                    MethodInfo[] methods = t.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly);


                    //Clear first
                    if (xn_element.Name == "ListTemplate")
                    {
                        XmlNodeList list = xdoc_manifest.DocumentElement.SelectNodes("ns:Receivers[@ListTemplateId='" + xn_element.Attributes["Type"].Value + "']/ns:Receiver[ns:Class='" + t.FullName + "' and ns:Assembly='" + a.FullName + "']", xns_manager);
                        foreach (XmlNode xn in list)
                            xn.ParentNode.RemoveChild(xn);
                    }
                    else if (xn_element.Name == "ContentType")
                    {
                        XmlNodeList list = xn_element.SelectNodes("ns:XmlDocuments/ns:XmlDocument/spe:Receivers/spe:Receiver[spe:Class='" + t.FullName + "' and spe:Assembly='" + a.FullName + "']", xns_manager);
                        foreach (XmlNode xn in list)
                            xn.ParentNode.RemoveChild(xn);
                    }


                    //re-add all matching methods
                    foreach (MethodInfo method in methods)
                    {
                        if (!method.IsVirtual || method.IsPrivate)
                            continue;

                        if (!isSigned)
                        {
                            //Method found but assembly is not signed. Signal this to the caller of this method.
                            return null;
                        }

                        //Find if this is already declared in manifest.

                        XmlNode xn_event_receivers = null;
                        if (xn_element.Name == "ListTemplate")
                        {
                            xn_event_receivers = xdoc_manifest.SelectSingleNode("ns:Elements/ns:Receivers[@ListTemplateId='" + xn_element.Attributes["Type"].Value + "']", xns_manager);
                            if (xn_event_receivers == null)
                            {
                                XmlElement el_receivers1 = xdoc_manifest.CreateElement("Receivers", Common.C_MS_SHAREPOINT_XMLNS);
                                el_receivers1.SetAttribute("ListTemplateId", xn_element.Attributes["Type"].Value);
                                //el_receivers1.SetAttribute("ListTemplateOwner", FeatureId);
                                xn_event_receivers = xdoc_manifest.DocumentElement.AppendChild(el_receivers1);
                            }
                        }
                        else if (xn_element.Name == "ContentType")
                        {
                            xn_event_receivers = xdoc_manifest.SelectSingleNode("ns:Elements/ns:ContentType[@ID='" + Common.GetXmlAttribute(xn_element as XmlElement, "ID") + "']/ns:XmlDocuments/ns:XmlDocument/spe:Receivers", xns_manager);
                            if (xn_event_receivers == null)
                            {
                                XmlNode xn_xmldocuments = xn_element.SelectSingleNode("ns:XmlDocuments", xns_manager);
                                if (xn_xmldocuments == null)
                                    xn_xmldocuments = xn_element.AppendChild(xdoc_manifest.CreateElement("XmlDocuments", Common.C_MS_SHAREPOINT_XMLNS));

                                XmlNode xn_document = xn_xmldocuments.SelectSingleNode("ns:XmlDocument", xns_manager);
                                if (xn_document == null)
                                {
                                    xn_document = xn_xmldocuments.AppendChild(xdoc_manifest.CreateElement("XmlDocument", Common.C_MS_SHAREPOINT_XMLNS));
                                    ((XmlElement)xn_document).SetAttribute("NamespaceURI", Common.C_MS_SHAREPOINT_EVENTS_NS);
                                }

                                XmlElement el_receivers2 = xdoc_manifest.CreateElement("spe", "Receivers", Common.C_MS_SHAREPOINT_EVENTS_NS);
                                xn_event_receivers = xn_document.AppendChild(el_receivers2);
                            }
                        }

                        if (xn_event_receivers != null)
                        {
                            string ns;
                            if (xn_element.Name == "ListTemplate")
                                ns = Common.C_MS_SHAREPOINT_XMLNS;
                            else
                                ns = Common.C_MS_SHAREPOINT_EVENTS_NS;

                            XmlElement el_receiver = xdoc_manifest.CreateElement("Receiver", ns);
                            XmlElement el_name = xdoc_manifest.CreateElement("Name", ns);
                            XmlElement el_type = xdoc_manifest.CreateElement("Type", ns);
                            XmlElement el_assembly = xdoc_manifest.CreateElement("Assembly", ns);
                            XmlElement el_class = xdoc_manifest.CreateElement("Class", ns);

                            el_name.InnerText = xn_element.Attributes["Name"].Value + "_" + method.Name;
                            el_type.InnerText = method.Name;
                            el_assembly.InnerText = a.FullName;
                            el_class.InnerText = t.FullName;

                            xn_event_receivers.AppendChild(el_receiver);

                            el_receiver.AppendChild(el_name);
                            el_receiver.AppendChild(el_type);
                            el_receiver.AppendChild(el_assembly);
                            el_receiver.AppendChild(el_class);

                        }
                    }
                }

            }

            foreach(XmlNode xn2 in xdoc_manifest.SelectNodes("ns:Elements/ns:Receivers", xns_manager))
            if (xn2.ChildNodes.Count == 0)
                xn2.ParentNode.RemoveChild(xn2);

            return new string[] { xdoc_feature.InnerXml, xdoc_manifest.InnerXml };

        }

    }

}
